import ansiColors, { StyleFunction } from 'ansi-colors'

export enum LoggerLevel {
  // Do not show any log output.
  Silent,
  // Only show errors.
  Error,
  // Only show warnings and errors.
  Warning,
  // Show warnings, errors, and an output file summary.
  Info,
  Log,
  // Log everything from info
  Debug,
  // This generates a torrent of log messages and was added to debug issues with file system drivers. It's not intended for general use.
  Verbose
}

export interface ILogger {
  log(this: void, message?: any, ...optionalParams: any[]): void
  info(this: void, message?: any, ...optionalParams: any[]): void
  warn(this: void, message?: any, ...optionalParams: any[]): void
  error(this: void, message?: any, ...optionalParams: any[]): void
  debug(this: void, message?: any, ...optionalParams: any[]): void
  trace(this: void, message?: any, ...optionalParams: any[]): void
  time(this: void, label?: string): void
  timeEnd(this: void, label?: string): void
  timeLog(this: void, label?: string, ...data: any[]): void
}

export const createLogger = (
  name: string,
  level: LoggerLevel = process.env.DEBUG ? LoggerLevel.Debug : LoggerLevel.Log
): ILogger => {
  const labeledArgs = (style: StyleFunction, args: any[]) => {
    if (Array.isArray(args)) {
      if (args.length > 0 && typeof args[0] === 'string') {
        return [style.bold(`[${name}]`) + ' ' + style(`${args[0]}`), ...args.slice(1)]
      } else {
        return [style.bold(`[${name}]`), ...args]
      }
    } else {
      return []
    }
  }

  const labeledArg = (style: StyleFunction, label?: string) => {
    return style.bold(`[${name}]`) + ' ' + style(`${label ?? ''}`)
  }

  let lastTime: number

  return {
    debug(...args) {
      if (level >= LoggerLevel.Debug) {
        if (lastTime === undefined) {
          lastTime = performance.now()
        } else {
          const current = performance.now()
          const time = current - lastTime
          lastTime = current
          const timeMessage = ansiColors.yellow(time >= 1000 ? `+${(time / 1000).toFixed(2)}s` : `+${time.toFixed(2)}ms`)
          if (typeof args[0] === 'string') {
            args[0] = timeMessage + ' ' + args[0]
          } else {
            args.unshift(timeMessage)
          }
        }
        args = labeledArgs(ansiColors.blue, args)
        console.debug(...args)
      }
    },
    log(...args) {
      if (level >= LoggerLevel.Log) {
        console.log(...labeledArgs(ansiColors.white, args))
      }
    },
    info(...args) {
      if (level >= LoggerLevel.Info) {
        console.info(...labeledArgs(ansiColors.cyan, args))
      }
    },
    warn(...args) {
      if (level >= LoggerLevel.Warning) {
        console.warn(...labeledArgs(ansiColors.yellow, args))
      }
    },
    error(...args) {
      if (level >= LoggerLevel.Error) {
        console.warn(...labeledArgs(ansiColors.red, args))
      }
    },
    trace(...args) {
      if (level >= LoggerLevel.Verbose) {
        console.trace(...labeledArgs(ansiColors.cyanBright, args))
      }
    },
    time(label) {
      if (level >= LoggerLevel.Log) {
        console.time(labeledArg(ansiColors.whiteBright, label))
      }
    },
    timeLog(label) {
      if (level >= LoggerLevel.Log) {
        console.timeLog(labeledArg(ansiColors.whiteBright, label))
      }
    },
    timeEnd(label) {
      if (level >= LoggerLevel.Log) {
        console.timeEnd(labeledArg(ansiColors.whiteBright, label))
      }
    }
  }
}
